﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text;
using System.Threading.Tasks;
using StyleMVVM.Data;
using StyleMVVM.Logging;

#if !DOT_NET
using Windows.Foundation.Metadata;

#endif

namespace StyleMVVM.Validation.Impl
{
	internal abstract class BaseValidationContext : IInternalValidationContext
	{
		private static readonly string supplemental = typeof(BaseValidationContext).FullName;

		protected string[] standards;
		protected readonly IValidationService validationService;
		protected readonly IReflectionService reflectionService;
		protected readonly WeakReference validationReference;

		protected readonly List<IValidationRule> rules = new List<IValidationRule>();

		protected readonly Dictionary<string, List<IValidationRule>> rulesByProperty =
			new Dictionary<string, List<IValidationRule>>();

		protected readonly Dictionary<string, List<IValidationRuleResult>> ruleResults =
			new Dictionary<string, List<IValidationRuleResult>>();

		protected readonly Dictionary<string, List<IValidationRuleResultPart>> ruleResultParts =
			new Dictionary<string, List<IValidationRuleResultPart>>();

		protected Dictionary<string, List<WeakReference>> propertyHandlers;
		protected List<WeakReference> stateChangeHandlers;
		protected List<WeakReference> validatingChangeHandlers;
		protected Dictionary<string, IInternalValidationContext> childValidationContexts;
		protected List<IInternalValidationContext> linkedContexts;
		protected Dictionary<IValidationRule, bool> asyncRuleRequest;

		protected ValidationState state = ValidationState.Valid;
		protected int warnings;
		protected int errors;
		protected int requireds;
		protected volatile bool internalSyncValidating = false;
		protected volatile bool validating;
		protected bool monitor = false;
		protected bool shared = false;
		protected string parentPropertyName;

		public BaseValidationContext(object validationObject,
		                             [ReadOnlyArray] string[] standards,
		                             IValidationService validationService,
		                             IValidationStandardsSelector standardsSelector,
		                             IReflectionService reflectionService,
		                             bool monitor,
		                             bool shared)
		{
			this.monitor = monitor;
			this.shared = shared;

			this.reflectionService = reflectionService;
			this.validationService = validationService;

			validationReference = new WeakReference(validationObject);

			StandardsSelector = standardsSelector;

			if (StandardsSelector != null)
			{
				try
				{
					Standards = StandardsSelector.Select(validationObject).ToArray();
				}
				catch (Exception exp)
				{
					Logger.Error("Exception thrown while executing standards selector: " + StandardsSelector.GetType().FullName,
					             supplemental,
					             exp);
				}
			}
			else
			{
				Standards = standards;
			}
		}

		public IValidationStandardsSelector StandardsSelector { get; private set; }

		public object ValidationObject
		{
			get { return validationReference.Target; }
		}

		public ValidationState State
		{
			get { return state; }
		}

		public bool Validating
		{
			get { return validating; }
		}

		protected bool InternalSyncValidating
		{
			get { return internalSyncValidating; }
			set
			{
				if (internalSyncValidating != value)
				{
					internalSyncValidating = value;

					SetValidating(value);
				}
			}
		}

		private void SetValidating(bool value)
		{
			if (validating != value)
			{
				bool setValue = false;

				if (value)
				{
					setValue = true;
				}
				else if (!internalSyncValidating &&
				         (asyncRuleRequest == null || asyncRuleRequest.Count == 0))
				{
					setValue = true;

					if (childValidationContexts != null)
					{
						foreach (IInternalValidationContext internalValidationContext in childValidationContexts.Values)
						{
							if (internalValidationContext != null &&
							    internalValidationContext.Validating)
							{
								setValue = false;
								break;
							}
						}
					}
				}

				if (setValue)
				{
					validating = value;

					RaiseValidatingChanged(value);
				}
			}
		}

		public IEnumerable<IValidationRuleResultPart> PropertyResults(string propertyName)
		{
			List<IValidationRuleResultPart> parts;

			if (ruleResultParts.TryGetValue(propertyName, out parts))
			{
				return parts;
			}

			return new IValidationRuleResultPart[] { };
		}

		public IEnumerable<IValidationRuleResult> RuleResults()
		{
			List<IValidationRuleResult> returnValue = new List<IValidationRuleResult>();

			if (ruleResults != null)
			{
				foreach (KeyValuePair<string, List<IValidationRuleResult>> keyValuePair in ruleResults)
				{
					returnValue.AddRange(keyValuePair.Value);
				}
			}

			return returnValue;
		}

		public virtual IEnumerable<IValidationContext> Children
		{
			get
			{
				if (childValidationContexts != null)
				{
					return childValidationContexts.Values.Where(x => x != null);
				}

				return new IValidationContext[] { };
			}
		}

		public IValidationContext ParentContext { get; private set; }

		public void LinkContex(IValidationContext context)
		{
			if (linkedContexts == null)
			{
				linkedContexts = new List<IInternalValidationContext>();
			}

			IInternalValidationContext internalValidationContext = context as IInternalValidationContext;

			if (internalValidationContext != null)
			{
				linkedContexts.Add(internalValidationContext);

				foreach (KeyValuePair<string, List<IValidationRuleResult>> keyValuePair in ruleResults)
				{
					foreach (IValidationRuleResult validationRuleResult in keyValuePair.Value)
					{
						internalValidationContext.AddIValidationRuleResult(validationRuleResult);
					}
				}

				internalValidationContext.ProcessChildValidatingChanged(validating);
			}
		}

		public void UnlinkContex(IValidationContext context)
		{
			IInternalValidationContext internalValidationContext = context as IInternalValidationContext;

			if (internalValidationContext != null && linkedContexts.Remove(internalValidationContext))
			{
				foreach (KeyValuePair<string, List<IValidationRuleResult>> keyValuePair in ruleResults)
				{
					foreach (IValidationRuleResult validationRuleResult in keyValuePair.Value)
					{
						internalValidationContext.RemoveIValidationRuleResult(validationRuleResult);
					}
				}

				internalValidationContext.ProcessChildValidatingChanged(false);
			}
		}

		[DefaultOverload]
		public void AddRules(params IValidationRule[] validationRules)
		{
			AddRules((IEnumerable<IValidationRule>)validationRules);
		}

		public abstract void AddRules(IEnumerable<IValidationRule> validationRules);

		[DefaultOverload]
		public void RemoveRules(params IValidationRule[] validationRules)
		{
			RemoveRules((IEnumerable<IValidationRule>)validationRules);
		}

		public abstract void RemoveRules(IEnumerable<IValidationRule> validationRules);

		public abstract void Validate();

		public void RegisterIValidatingChangedHandler(IValidatingChangedHandler handler)
		{
			if (validatingChangeHandlers == null)
			{
				validatingChangeHandlers = new List<WeakReference>();
			}

			validatingChangeHandlers.Add(new WeakReference(handler));
		}

		public void UnregisterIValidatingChangedHandler(IValidatingChangedHandler handler)
		{
			if (validatingChangeHandlers != null)
			{
				foreach (WeakReference validatingChangeHandler in validatingChangeHandlers)
				{
					if (validatingChangeHandler.Target == handler)
					{
						validatingChangeHandlers.Remove(validatingChangeHandler);
						break;
					}
				}

				if (validatingChangeHandlers.Count == 0)
				{
					validatingChangeHandlers = null;
				}
			}
		}

		public void RegisterValidationStateChangedHandler(IValidationStateChangedHandler handler)
		{
			if (stateChangeHandlers == null)
			{
				stateChangeHandlers = new List<WeakReference>();
			}

			stateChangeHandlers.Add(new WeakReference(handler));
		}

		public void UnregisterValidationStateChangedHandler(IValidationStateChangedHandler handler)
		{
			if (stateChangeHandlers != null)
			{
				foreach (WeakReference stateChangeHandler in stateChangeHandlers)
				{
					if (stateChangeHandler.Target == handler)
					{
						stateChangeHandlers.Remove(stateChangeHandler);
						break;
					}
				}

				if (stateChangeHandlers.Count == 0)
				{
					stateChangeHandlers = null;
				}
			}
		}

		public void RegisterPropertyValidationHandler(string propertyName, IPropertyValidationHandler handler)
		{
			if (propertyHandlers == null)
			{
				propertyHandlers = new Dictionary<string, List<WeakReference>>();
			}
			List<WeakReference> handlerList;

			if (!propertyHandlers.TryGetValue(propertyName, out handlerList))
			{
				handlerList = new List<WeakReference>();

				propertyHandlers.Add(propertyName, handlerList);
			}

			handlerList.Add(new WeakReference(handler));
		}

		public void UnregisterPropertyValidationHandler(string propertyName, IPropertyValidationHandler handler)
		{
			if (propertyHandlers != null)
			{
				List<WeakReference> handlerList;

				if (propertyHandlers.TryGetValue(propertyName, out handlerList))
				{
					foreach (WeakReference weakReference in handlerList)
					{
						if (weakReference.Target == handler)
						{
							handlerList.Remove(weakReference);
							break;
						}
					}
				}
			}
		}

		public string[] Standards
		{
			get { return standards; }
			set
			{
				standards = value;

				SetupRulesAndProperties(standards);
			}
		}

		public void SetParent(IInternalValidationContext validationContext, string propertyName)
		{
			ClearResultsInParent();

			ParentContext = validationContext;

			parentPropertyName = propertyName;

			if (validationContext != null)
			{
				foreach (KeyValuePair<string, List<IValidationRuleResult>> keyValuePair in ruleResults)
				{
					foreach (ValidationRuleResult validationRuleResult in keyValuePair.Value)
					{
						validationContext.AddIValidationRuleResult(validationRuleResult);
					}
				}

				validationContext.ProcessChildValidatingChanged(validating);
			}
		}

		public void AddIValidationRuleResult(IValidationRuleResult ruleResult)
		{
			List<IValidationRuleResult> ruleResultList;

			if (!ruleResults.TryGetValue(ruleResult.FullRuleName(), out ruleResultList))
			{
				ruleResultList = new List<IValidationRuleResult>();

				ruleResults.Add(ruleResult.FullRuleName(), ruleResultList);
			}

			ruleResultList.Add(ruleResult);

			if (ruleResult.RootPropertyPath != null)
			{
				if (parentPropertyName != null)
				{
					ruleResult.RootPropertyPath = string.Concat(parentPropertyName, ".", ruleResult.RootPropertyPath);
				}
			}
			else
			{
				ruleResult.RootPropertyPath = parentPropertyName;
			}

			bool hasRootPath = !string.IsNullOrEmpty(ruleResult.RootPropertyPath);

			List<Tuple<string, List<IValidationRuleResultPart>>> partLists = null;

			foreach (IValidationRuleResultPart validationRuleResultPart in ruleResult.ResultParts())
			{
				List<IValidationRuleResultPart> ruleResultPartList;
				string fullPropertyPath = validationRuleResultPart.PartName;

				if (hasRootPath)
				{
					fullPropertyPath = string.Concat(ruleResult.RootPropertyPath, ".", fullPropertyPath);
				}

				if (!ruleResultParts.TryGetValue(fullPropertyPath, out ruleResultPartList))
				{
					ruleResultPartList = new List<IValidationRuleResultPart>();

					ruleResultParts.Add(fullPropertyPath, ruleResultPartList);
				}

				ruleResultPartList.Add(validationRuleResultPart);

				if (partLists == null && propertyHandlers != null)
				{
					partLists = new List<Tuple<string, List<IValidationRuleResultPart>>>();
				}

				if (partLists != null &&
				    partLists.FirstOrDefault(x => x.Item1 == fullPropertyPath) == null)
				{
					partLists.Add(new Tuple<string, List<IValidationRuleResultPart>>(
						              fullPropertyPath, ruleResultPartList));
				}
			}

			switch (ruleResult.State)
			{
				case ValidationState.ValidWithWarning:
					warnings++;
					break;
				case ValidationState.Invalid:
					errors++;
					break;
				case ValidationState.InvalidRequired:
					requireds++;
					break;
			}

			SetState();

			if (partLists != null)
			{
				ProcessPropertyValidation(partLists);
			}

			IInternalValidationContext parentContext = ParentContext as IInternalValidationContext;

			if (parentContext != null)
			{
				parentContext.AddIValidationRuleResult(ruleResult);
			}

			if (linkedContexts != null)
			{
				foreach (IInternalValidationContext internalValidationContext in linkedContexts)
				{
					internalValidationContext.AddIValidationRuleResult(ruleResult);
				}
			}
		}

		protected void ProcessPropertyValidation(IEnumerable<Tuple<string, List<IValidationRuleResultPart>>> partLists)
		{
			foreach (Tuple<string, List<IValidationRuleResultPart>> partList in partLists)
			{
				List<WeakReference> handlers;

				if (propertyHandlers != null && propertyHandlers.TryGetValue(partList.Item1, out handlers))
				{
					List<WeakReference> deleteList = null;

					foreach (WeakReference weakReference in handlers)
					{
						IPropertyValidationHandler handler = weakReference.Target as IPropertyValidationHandler;

						if (handler != null)
						{
							try
							{
								handler.ProcessPropertyValidation(partList.Item2);
							}
							catch (Exception exp)
							{
								Logger.Error("Exception thrown while calling validation property handler", supplemental, exp);
							}
						}
						else
						{
							if (deleteList == null)
							{
								deleteList = new List<WeakReference>();
							}

							deleteList.Add(weakReference);
						}
					}

					if (deleteList != null)
					{
						foreach (WeakReference weakReference in deleteList)
						{
							handlers.Remove(weakReference);
						}

						if (handlers.Count == 0)
						{
							propertyHandlers.Remove(partList.Item1);
						}
					}
				}
			}
		}

		public void RemoveIValidationRuleResult(IValidationRuleResult ruleResult)
		{
			if (ruleResult == null)
			{
				return;
			}

			switch (ruleResult.State)
			{
				case ValidationState.ValidWithWarning:
					warnings--;
					break;
				case ValidationState.Invalid:
					errors--;
					break;
				case ValidationState.InvalidRequired:
					requireds--;
					break;
			}

			SetState();

			List<IValidationRuleResult> ruleResultList;

			if (ruleResults.TryGetValue(ruleResult.FullRuleName(), out ruleResultList))
			{
				foreach (IValidationRuleResult validationRuleResult in ruleResultList)
				{
					if (ValidationRuleResult.AreEqual(validationRuleResult, ruleResult))
					{
						ruleResultList.Remove(validationRuleResult);
						break;
					}
				}
			}

			if (ruleResult.RootPropertyPath != null)
			{
				if (parentPropertyName != null)
				{
					ruleResult.RootPropertyPath = string.Concat(parentPropertyName, ".", ruleResult.RootPropertyPath);
				}
			}
			else
			{
				ruleResult.RootPropertyPath = parentPropertyName;
			}

			bool hasRootPath = !string.IsNullOrEmpty(ruleResult.RootPropertyPath);

			List<Tuple<string, List<IValidationRuleResultPart>>> partLists =
				new List<Tuple<string, List<IValidationRuleResultPart>>>();

			foreach (IValidationRuleResultPart validationRuleResultPart in ruleResult.ResultParts())
			{
				List<IValidationRuleResultPart> ruleResultPartList;
				string fullPropertyPath = validationRuleResultPart.PartName;

				if (hasRootPath)
				{
					fullPropertyPath = string.Concat(ruleResult.RootPropertyPath, ".", fullPropertyPath);
				}

				if (ruleResultParts.TryGetValue(fullPropertyPath, out ruleResultPartList))
				{
					foreach (IValidationRuleResultPart ruleResultPart in ruleResultPartList)
					{
						if (ValidationRuleResultPart.AreEqual(validationRuleResultPart, ruleResultPart))
						{
							ruleResultPartList.Remove(ruleResultPart);

							if (partLists.FirstOrDefault(x => x.Item1 == fullPropertyPath) == null)
							{
								partLists.Add(new Tuple<string, List<IValidationRuleResultPart>>(fullPropertyPath,
								                                                                 ruleResultPartList));
							}
							break;
						}
					}
				}
			}

			ProcessPropertyValidation(partLists);

			IInternalValidationContext parentContext = ParentContext as IInternalValidationContext;

			if (parentContext != null)
			{
				parentContext.RemoveIValidationRuleResult(ruleResult);
			}

			if (linkedContexts != null)
			{
				foreach (IInternalValidationContext internalValidationContext in linkedContexts)
				{
					internalValidationContext.RemoveIValidationRuleResult(ruleResult);
				}
			}
		}

		public void ClearResultsInParent()
		{
			IInternalValidationContext parentContext = ParentContext as IInternalValidationContext;

			if (parentContext != null)
			{
				foreach (KeyValuePair<string, List<IValidationRuleResult>> keyValuePair in ruleResults)
				{
					foreach (IValidationRuleResult validationRuleResult in keyValuePair.Value)
					{
						parentContext.RemoveIValidationRuleResult(validationRuleResult);
					}
				}
			}
		}

		public void ProcessChildValidatingChanged(bool childValidating)
		{
			SetValidating(childValidating);
		}

		protected void SetState()
		{
			if (errors > 0)
			{
				if (state != ValidationState.Invalid)
				{
					var oldState = state;

					state = ValidationState.Invalid;

					RaiseStateChange(oldState, state);
				}
			}
			else if (requireds > 0)
			{
				if (state != ValidationState.InvalidRequired)
				{
					var oldState = state;

					state = ValidationState.InvalidRequired;

					RaiseStateChange(oldState, state);
				}
			}
			else if (warnings > 0)
			{
				if (state != ValidationState.ValidWithWarning)
				{
					var oldState = state;

					state = ValidationState.ValidWithWarning;

					RaiseStateChange(oldState, state);
				}
			}
			else
			{
				if (state != ValidationState.Valid)
				{
					var oldState = state;

					state = ValidationState.Valid;

					RaiseStateChange(oldState, state);
				}
			}
		}

		protected void RaiseValidatingChanged(bool value)
		{
			if (validatingChangeHandlers != null)
			{
				foreach (WeakReference validatingChangeHandler in validatingChangeHandlers)
				{
					IValidatingChangedHandler handler = validatingChangeHandler.Target as IValidatingChangedHandler;

					if (handler != null)
					{
						try
						{
							handler.ValidatingChanged(this, value);
						}
						catch (Exception exp)
						{
							Logger.Error("Exception thrown while raising ValidatingChanged: " + handler.GetType().FullName, supplemental, exp);
						}
					}
				}
			}
			IInternalValidationContext internalParentContext = ParentContext as IInternalValidationContext;

			if (internalParentContext != null)
			{
				internalParentContext.ProcessChildValidatingChanged(value);
			}

			if (linkedContexts != null)
			{
				foreach (IInternalValidationContext internalValidationContext in linkedContexts)
				{
					internalValidationContext.ProcessChildValidatingChanged(value);
				}
			}
		}

		protected void RaiseStateChange(ValidationState oldState, ValidationState newState)
		{
			if (stateChangeHandlers != null)
			{
				foreach (WeakReference stateChangeHandler in stateChangeHandlers)
				{
					IValidationStateChangedHandler handler = stateChangeHandler.Target as IValidationStateChangedHandler;

					if (handler != null)
					{
						try
						{
							handler.StateChanged(this, newState);
						}
						catch (Exception exp)
						{
							Logger.Error("Exception thrown while valling StateChanged: " + handler.GetType().FullName, supplemental, exp);
						}
					}
				}
			}
		}

		protected void InternalAddRules(object target, IEnumerable<IValidationRule> validationRules)
		{
			foreach (IValidationRule validationRule in validationRules)
			{
				foreach (string property in validationRule.DependentProperties())
				{
					AddRuleByProperty(property, validationRule);
				}

				rules.Add(validationRule);

				IAsyncValidationRule asyncValidationRule = validationRule as IAsyncValidationRule;

				if (asyncValidationRule != null)
				{
					ExecuteAsyncRule(target, asyncValidationRule);
				}
				else if (validationRule is ISyncValidationRule)
				{
					ExecuteSyncRule(target, validationRule as ISyncValidationRule);
				}
			}
		}

		protected void InternalRemoveRule(object target, IEnumerable<IValidationRule> validationRules)
		{
			foreach (IValidationRule validationRule in validationRules)
			{
				List<IValidationRuleResult> results;

				if (ruleResults.TryGetValue(validationRule.FullName(), out results))
				{
					foreach (IValidationRuleResult validationRuleResult in results.ToArray())
					{
						RemoveIValidationRuleResult(validationRuleResult);
					}
				}

				rules.Remove(validationRule);

				foreach (string dependentProperty in validationRule.DependentProperties())
				{
					RemoveRuleByProperty(dependentProperty, validationRule);
				}
			}
		}

		protected virtual void SetupRulesAndProperties(string[] standards)
		{
			object target = ValidationObject;

			if (target != null)
			{
				ClearResults();

				this.standards = standards;

				rules.Clear();
				rulesByProperty.Clear();

				IEnumerable<IValidationRule> validationRules =
					validationService.FetchValidationRules(target.GetType(), standards);

				foreach (IValidationRule validationRule in validationRules)
				{
					foreach (string property in validationRule.DependentProperties())
					{
						AddRuleByProperty(property, validationRule);
					}

					rules.Add(validationRule);
				}

				foreach (string propertyName in validationService.FetchPropertiesToValidate(target.GetType(), standards))
				{
					if (childValidationContexts == null)
					{
						childValidationContexts = new Dictionary<string, IInternalValidationContext>();
					}

					object childObject = reflectionService.GetPropertyValue(target, propertyName);

					if (childObject != null)
					{
						IInternalValidationContext newChildContext =
							validationService.GetValidationContext(childObject, monitor, shared) as IInternalValidationContext;

						if (newChildContext != null)
						{
							newChildContext.Standards = standards;

							newChildContext.SetParent(this, propertyName);

							childValidationContexts[propertyName] = newChildContext;
						}
					}
					else
					{
						childValidationContexts[propertyName] = null;
					}
				}

				if (childValidationContexts != null && childValidationContexts.Count == 0)
				{
					childValidationContexts = null;
				}
			}
		}

		protected void AddRuleByProperty(string propertyName, IValidationRule rule)
		{
			int index = propertyName.LastIndexOf('.');

			if (index > 0)
			{
				AddRuleByProperty(propertyName.Substring(0, index), rule);
			}

			List<IValidationRule> propertyRuleList;

			if (!rulesByProperty.TryGetValue(propertyName, out propertyRuleList))
			{
				propertyRuleList = new List<IValidationRule>();

				rulesByProperty.Add(propertyName, propertyRuleList);
			}

			propertyRuleList.Add(rule);
		}

		protected void RemoveRuleByProperty(string propertyName, IValidationRule rule)
		{
			int index = propertyName.LastIndexOf('.');

			if (index > 0)
			{
				RemoveRuleByProperty(propertyName.Substring(0, index), rule);
			}

			List<IValidationRule> propertyRuleList;

			if (rulesByProperty.TryGetValue(propertyName, out propertyRuleList))
			{
				propertyRuleList.Remove(rule);
			}
		}

		protected void RunAllRules(object targetObject)
		{
			InternalSyncValidating = true;

			foreach (IValidationRule validationRule in rules)
			{
				if (validationRule is ISyncValidationRule)
				{
					ExecuteSyncRule(targetObject, validationRule as ISyncValidationRule);
				}
				else if (validationRule is IAsyncValidationRule)
				{
					ExecuteAsyncRule(targetObject, validationRule as IAsyncValidationRule);
				}
				else
				{
					Logger.Error(string.Format(
						"Rule {0} must implement either ISyncValidationRule or IAsyncValidationRule", validationRule.FullName()),
					             supplemental);
				}
			}

			InternalSyncValidating = false;
		}

		protected void RunPropertyRules(object targetObject, string propertyName)
		{
			List<IValidationRule> rules;

			if (rulesByProperty.TryGetValue(propertyName, out rules))
			{
				InternalSyncValidating = true;

				foreach (IValidationRule validationRule in rules)
				{
					if (validationRule is ISyncValidationRule)
					{
						ExecuteSyncRule(targetObject, validationRule as ISyncValidationRule);
					}
					else if (validationRule is IAsyncValidationRule)
					{
						ExecuteAsyncRule(targetObject, validationRule as IAsyncValidationRule);
					}
					else
					{
						Logger.Error(string.Format(
							"Rule {0} must implement either ISyncValidationRule or IAsyncValidationRule", validationRule.FullName()),
						             supplemental);
					}
				}

				InternalSyncValidating = false;
			}
		}

		protected async void ExecuteAsyncRule(object targetObject, IAsyncValidationRule asyncValidationRule)
		{
			if (asyncRuleRequest == null)
			{
				asyncRuleRequest = new Dictionary<IValidationRule, bool>();
			}

			if (targetObject != null)
			{
				bool requestRun;

				if (asyncRuleRequest.TryGetValue(asyncValidationRule, out requestRun))
				{
					if (!requestRun)
					{
						asyncRuleRequest[asyncValidationRule] = true;
					}
				}
				else
				{
					try
					{
						await RunAsyncRuleTillNoMoreRequests(targetObject, asyncValidationRule);
					}
					catch (Exception exp)
					{
						Logger.Error("Exception thrown while calling async validation rule: " + asyncValidationRule.FullName(),
						             supplemental,
						             exp);
					}

					SetValidating(false);
				}
			}
		}

		protected async Task RunAsyncRuleTillNoMoreRequests(object targetObject, IAsyncValidationRule asyncValidationRule)
		{
			asyncRuleRequest[asyncValidationRule] = false;
			bool keepRunning = true;

			while (keepRunning)
			{
				RuleExecutionContext ruleExecutionContext = new RuleExecutionContext(ValidationObject);

				await asyncValidationRule.Validate(ruleExecutionContext);

				if (asyncRuleRequest[asyncValidationRule] == false)
				{
					asyncRuleRequest.Remove(asyncValidationRule);

					ProcessRuleResult(asyncValidationRule, ruleExecutionContext);

					keepRunning = false;
				}
				else
				{
					asyncRuleRequest[asyncValidationRule] = false;
				}
			}
		}

		protected void ExecuteSyncRule(object targetObject, ISyncValidationRule rule)
		{
			if (targetObject != null)
			{
				RuleExecutionContext ruleExecutionContext = new RuleExecutionContext(ValidationObject);

				try
				{
					rule.Validate(ruleExecutionContext);
				}
				catch (Exception exp)
				{
					Logger.Error("Exception thrown while executing rule: " + rule.Name, supplemental, exp);

					ruleExecutionContext.State = ValidationState.Invalid;
				}

				ProcessRuleResult(rule, ruleExecutionContext);
			}
		}

		protected void ProcessRuleResult(IValidationRule rule, RuleExecutionContext ruleExecutionContext)
		{
			ValidationRuleResult ruleResult = new ValidationRuleResult(rule, ruleExecutionContext);
			bool add = true;
			List<IValidationRuleResult> ruleResultList;

			if (ruleResults.TryGetValue(ruleResult.FullRuleName(), out ruleResultList))
			{
				IValidationRuleResult oldResult = null;

				foreach (IValidationRuleResult validationRuleResult in ruleResultList)
				{
					if (validationRuleResult.FullRuleName() == ruleResult.FullRuleName() &&
					    validationRuleResult.ValidationObject == ruleResult.ValidationObject)
					{
						oldResult = validationRuleResult;
						break;
					}
				}

				if (ruleResult.State == ValidationState.Valid)
				{
					add = false;

					if (oldResult != null)
					{
						oldResult.RootPropertyPath = null;

						RemoveIValidationRuleResult(oldResult);
					}
				}
				else if (ValidationRuleResult.AreEqual(oldResult, ruleResult))
				{
					add = false;
				}
				else if (oldResult != null)
				{
					oldResult.RootPropertyPath = null;

					RemoveIValidationRuleResult(oldResult);
				}
			}

			if (add)
			{
				AddIValidationRuleResult(ruleResult);
			}
		}

		protected void ClearResults()
		{
			foreach (KeyValuePair<string, List<IValidationRuleResult>> keyValuePair in ruleResults.ToArray())
			{
				foreach (IValidationRuleResult ruleResult in keyValuePair.Value.ToArray())
				{
					ruleResult.RootPropertyPath = null;

					RemoveIValidationRuleResult(ruleResult);
				}
			}
		}
	}
}